namespace IntegrationTests;

public class ConnectionTests : IClassFixture<DatabaseFixture>
{
	public ConnectionTests(DatabaseFixture database)
	{
	}

	[Fact]
	public void GotInfoMessageForNonExistentTable()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();

		var gotEvent = false;
		connection.InfoMessage += (s, a) =>
		{
			gotEvent = true;
#if MYSQL_DATA
			Assert.Single(a.errors);
			Assert.Equal((int) MySqlErrorCode.BadTable, a.errors[0].Code);
#else
			Assert.Single(a.Errors);
			Assert.Equal(MySqlErrorCode.BadTable, a.Errors[0].ErrorCode);
#endif
		};

		connection.Execute(@"drop table if exists table_does_not_exist;");
		Assert.True(gotEvent);
	}

	[Fact]
	public void GotInfoMessageForNonExistentTableInTransaction()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();
		using var transaction = connection.BeginTransaction();

		var gotEvent = false;
		connection.InfoMessage += (s, a) =>
		{
			gotEvent = true;
#if MYSQL_DATA
			Assert.Single(a.errors);
			Assert.Equal((int) MySqlErrorCode.BadTable, a.errors[0].Code);
#else
			Assert.Single(a.Errors);
			Assert.Equal(MySqlErrorCode.BadTable, a.Errors[0].ErrorCode);
#endif
		};

		connection.Execute(@"drop table if exists table_does_not_exist;", transaction: transaction);
		Assert.True(gotEvent);
	}

	[Fact]
	public void NoInfoMessageWhenNotLastStatementInBatch()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();

		var gotEvent = false;
		connection.InfoMessage += (s, a) =>
		{
			gotEvent = true;

#if MYSQL_DATA
			// seeming bug in Connector/NET raises an event with no errors
			Assert.Empty(a.errors);
#endif
		};

		connection.Execute(@"drop table if exists table_does_not_exist; select 1;");
#if MYSQL_DATA
		Assert.True(gotEvent);
#else
		Assert.False(gotEvent);
#endif
	}

	[Fact]
	public void DefaultConnectionStringIsEmpty()
	{
		using var connection = new MySqlConnection();
		Assert.Equal("", connection.ConnectionString);
	}

	[Fact]
	public void InitializeWithNullConnectionString()
	{
		using var connection = new MySqlConnection(default(string));
		Assert.Equal("", connection.ConnectionString);
	}

	[Fact]
	public void SetConnectionStringToNull()
	{
		using var connection = new MySqlConnection();
		connection.ConnectionString = null;
		Assert.Equal("", connection.ConnectionString);
	}

	[Fact]
	public void SetConnectionStringToEmptyString()
	{
		using var connection = new MySqlConnection();
		connection.ConnectionString = "";
		Assert.Equal("", connection.ConnectionString);
	}

	[SkippableFact(MySqlData = "Throws NullReferenceException")]
	public void ServerVersionThrows()
	{
		using var connection = new MySqlConnection();
		Assert.Throws<InvalidOperationException>(() => connection.ServerVersion);
	}

	[SkippableFact(MySqlData = "Throws NullReferenceException")]
	public void ServerThreadThrows()
	{
		using var connection = new MySqlConnection();
		Assert.Throws<InvalidOperationException>(() => connection.ServerThread);
	}

	[Fact]
	public void DatabaseIsEmptyString()
	{
		using var connection = new MySqlConnection();
		Assert.Equal("", connection.Database);
	}

	[Fact]
	public void DataSourceIsEmptyString()
	{
		using var connection = new MySqlConnection();
		Assert.Equal("", connection.DataSource);
	}

	[Fact]
	public void ConnectionTimeoutDefaultValue()
	{
		using var connection = new MySqlConnection();
		Assert.Equal(15, connection.ConnectionTimeout);
	}

	[Fact]
	public void ConnectionTimeoutDefaultValueAfterOpen()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();
		Assert.Equal(15, connection.ConnectionTimeout);
	}

	[Fact]
	public void ConnectionTimeoutExplicitValue()
	{
		using var connection = new MySqlConnection("Connection Timeout=30");
		Assert.Equal(30, connection.ConnectionTimeout);
	}

	[Fact]
	public void ConnectionTimeoutExplicitValueAfterOpen()
	{
		var csb = AppConfig.CreateConnectionStringBuilder();
		csb.ConnectionTimeout = 30;
		using var connection = new MySqlConnection(csb.ConnectionString);
		connection.Open();
		Assert.Equal(30, connection.ConnectionTimeout);
	}

	[Fact]
	public void CloneClonesConnectionString()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		using var connection2 = (MySqlConnection) connection.Clone();
		Assert.Equal(connection.ConnectionString, connection2.ConnectionString);
#if !MYSQL_DATA
		Assert.Equal(AppConfig.ConnectionString, connection2.ConnectionString);
#endif
	}

	[Fact]
	public void CloneIsClosed()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();
		using var connection2 = (MySqlConnection) connection.Clone();
		Assert.Equal(ConnectionState.Closed, connection2.State);
	}

	[Fact]
	public void CloneDoesNotDisclosePassword()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();
		using var connection2 = (MySqlConnection) connection.Clone();
		Assert.Equal(connection.ConnectionString, connection2.ConnectionString);
		Assert.DoesNotContain("password", connection2.ConnectionString, StringComparison.OrdinalIgnoreCase);
	}

#if !MYSQL_DATA
	[Theory]
	[InlineData(false)]
	[InlineData(true)]
	public void CloneWithUsesNewConnectionString(bool openConnection)
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		if (openConnection)
			connection.Open();
		using var connection2 = connection.CloneWith("user=root;password=pass;server=example.com;database=test");
		Assert.Equal("Server=example.com;User ID=root;Password=pass;Database=test", connection2.ConnectionString);
	}

	[Fact]
	public void CloneWithUsesExistingPassword()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		var newConnectionString = "user=root;server=example.com;database=test";
		using var connection2 = connection.CloneWith(newConnectionString);

		var builder = new MySqlConnectionStringBuilder(newConnectionString)
		{
			Password = AppConfig.CreateConnectionStringBuilder().Password,
		};
		Assert.Equal(builder.ConnectionString, connection2.ConnectionString);
	}

	[Theory]
	[InlineData(false)]
	[InlineData(true)]
	public void CloneWithDoesNotDiscloseExistingPassword(bool persistSecurityInfo)
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();

		var newConnectionString = "user=root;server=example.com;database=test;Persist Security Info=" + persistSecurityInfo;
		using var connection2 = connection.CloneWith(newConnectionString);

		var builder = new MySqlConnectionStringBuilder(newConnectionString);
		Assert.Equal(builder.ConnectionString, connection2.ConnectionString);
	}

	[Theory]
	[InlineData(false)]
	[InlineData(true)]
	public void CloneWithDoesDiscloseExistingPasswordIfPersistSecurityInfo(bool persistSecurityInfo)
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString + ";Persist Security Info=true");
		connection.Open();

		var newConnectionString = "user=root;server=example.com;database=test;Persist Security Info=" + persistSecurityInfo;
		using var connection2 = connection.CloneWith(newConnectionString);

		var builder = new MySqlConnectionStringBuilder(newConnectionString)
		{
			Password = AppConfig.CreateConnectionStringBuilder().Password,
		};
		Assert.Equal(builder.ConnectionString, connection2.ConnectionString);
	}

	[Fact]
	public void CloneWithCopiesExistingPassword()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		connection.Open();

		var builder = AppConfig.CreateConnectionStringBuilder();
		builder.Password = "";
		using var connection2 = connection.CloneWith(builder.ConnectionString);
		connection2.Open();
		Assert.Equal(ConnectionState.Open, connection2.State);
	}

	[Fact]
	public async Task ResetConnectionThrowsIfNotOpen()
	{
		using var connection = new MySqlConnection(AppConfig.ConnectionString);
		await Assert.ThrowsAsync<InvalidOperationException>(async () => await connection.ResetConnectionAsync());
	}

	[SkippableFact(ServerFeatures.ResetConnection)]
	public async Task ResetConnectionClearsUserVariables()
	{
		var csb = AppConfig.CreateConnectionStringBuilder();
		csb.AllowUserVariables = true;
		using var connection = new MySqlConnection(csb.ConnectionString);
		await connection.OpenAsync();

		connection.Execute("set @temp_var = 1;");
		var tempVar = connection.ExecuteScalar<int?>("select @temp_var;");
		Assert.Equal(1, tempVar);

		await connection.ResetConnectionAsync();

		tempVar = connection.ExecuteScalar<int?>("select @temp_var;");
		Assert.Null(tempVar);
	}
#endif
}
